﻿namespace Liger.Web.Javascript
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Liger.Web.Common.Extensions;
    using System.Globalization;
    using System.Web.Script.Serialization;

    public class JavascriptObjectWriter : IJavascriptObjectWriter
    {

        private bool propertyAppended = false;
        private bool objectAppended = false;
        private bool arrayAppended = false;
        private bool objectIncludedReturnName = false;
        private bool arrayIncludedReturnName = false;

        private StringBuilder writer;
        public JavascriptObjectWriter()
            : this(new StringBuilder())
        {
        }
        public JavascriptObjectWriter(StringBuilder writer)
        {
            this.writer = writer;
        }
        public JavascriptObjectWriter(bool? PropertyTitleCase)
        {
            this.PropertyTitleCase = PropertyTitleCase;
        }
        /// <summary>
        /// 属性名是否首字母大写
        /// </summary>
        public bool? PropertyTitleCase { get; set; }

        /// <summary>
        /// 属性名是否首字母小写
        /// </summary>
        public bool? PropertyVariableCase { get; set; }

        private string FormatPropertyName(string name)
        {
            if (name.IsNullOrEmpty()) return name;

            if (PropertyTitleCase.HasValue && PropertyTitleCase.Value)
            {
                return name.ToTitleCase();
            }
            if (PropertyVariableCase.HasValue && PropertyVariableCase.Value)
            {
                return name.ToVariableCase();
            }
            return name;
        }


        #region script of jQuery plugin
        public IJavascriptObjectWriter WriteJQueryOnLoad()
        {
            writer.Append(@"jQuery(document).ready(function(){
                ");
            return this;
        }

        public IJavascriptObjectWriter WriteEndJQueryOnLoad()
        {
            writer.Append(@"});
");
            return this;
        }
        public IJavascriptObjectWriter WriteJQueryPlugin(string ID, string PluginName)
        {
            WriteJQueryPlugin(ID, PluginName, null);
            return this;
        }
        public IJavascriptObjectWriter WriteJQueryPlugin(string ID, string PluginName, string ReturnName)
        { 
            if(ReturnName.HasValue())
            {
                writer.Append("var {0} = ".FormatWith(ReturnName));
            }

            writer.Append("jQuery('#{0}').{1}(".FormatWith(ID, PluginName));
            return this;
        }

        public IJavascriptObjectWriter WriteEndJQueryPlugin()
        {
            writer.Append(@");
");
            return this;
        }
        #endregion

        #region Write Object
        public IJavascriptObjectWriter WriteOjbect()
        { 
            WriteOjbect(null);
            return this;
        }

        public IJavascriptObjectWriter WriteOjbect(string ReturnName)
        { 
            objectIncludedReturnName = ReturnName.HasValue();
            if (objectIncludedReturnName)
            {
                writer.Append("var {0} = ".FormatWith(ReturnName));
            }
            else
            {
                if (arrayAppended && objectAppended)
                {
                    writer.Append(",");
                }
            }
            writer.Append("{");

            propertyAppended = false;
            objectAppended = true;
            return this;
        }

        public IJavascriptObjectWriter WriteEndOjbect()
        { 
            if (objectIncludedReturnName)
            {
                writer.Append("};");
            }
            else
            {
                writer.Append("}");
            }
            propertyAppended = false; 
            return this;
        }
        #endregion

        #region Write Array
        public IJavascriptObjectWriter WriteArray()
        { 
            WriteArray(null);
            return this;
        }

        public IJavascriptObjectWriter WriteArray(string ReturnName)
        { 
            arrayIncludedReturnName = ReturnName.HasValue();
            if (arrayIncludedReturnName)
            {
                writer.Append("var {0} = ".FormatWith(ReturnName));
            }
            writer.Append("[");
            arrayAppended = true;
            objectAppended = false;
            return this;
        }

        public IJavascriptObjectWriter WriteEndArray()
        { 
            if (arrayIncludedReturnName)
            {
                writer.Append("];");
            }
            else
            {
                writer.Append("]");
            }
            arrayAppended = false;
            objectAppended = false;
            return this;
        }
        #endregion

        #region Write Property With Value
        /// <summary>
        /// Write the specified key value pair to the end of this instance.
        /// </summary>
        /// <param name="keyValuePair">The key value pair.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string keyValuePair)
        {
            if (!string.IsNullOrEmpty(keyValuePair))
            {
                if (propertyAppended)
                    writer.Append(",");
                writer.Append(keyValuePair);

                propertyAppended = true;
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, string value)
        { 
            if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(value))
            {
                name = FormatPropertyName(name);

                string formattedValue = QuoteString(value);

                WriteProperty("{0}:'{1}'".FormatWith(name, formattedValue));
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and nullable value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WritePropertyNullableString(string name, string value)
        {

            if (!string.IsNullOrEmpty(name) && value != null)
            {
                name = FormatPropertyName(name);

                string formattedValue = QuoteString(value);

                WriteProperty("{0}:'{1}'".FormatWith(name, formattedValue));
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, int value)
        {
            if (!string.IsNullOrEmpty(name))
            {
                name = FormatPropertyName(name);

                WriteProperty("{0}:{1}".FormatWith(name, value));
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, int value, int defaultValue)
        {
            if (value != defaultValue)
            {
                WriteProperty(name, value);
            }
            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, int? value)
        {
            if (value.HasValue)
            {
                WriteProperty(name, value.Value);
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, double value)
        {
            if (!string.IsNullOrEmpty(name))
            {
                name = FormatPropertyName(name);

                WriteProperty("{0}:'{1}'".FormatWith(name, value));
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, double? value)
        {
            if (value.HasValue)
            {
                WriteProperty(name, value.Value);
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, decimal value)
        {
            if (!string.IsNullOrEmpty(name))
            {
                name = FormatPropertyName(name);

                WriteProperty("{0}:'{1}'".FormatWith(name, value));
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, decimal? value)
        {
            if (value.HasValue)
            {
                WriteProperty(name, value.Value);
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">if set to <c>true</c> [value].</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, bool value)
        {
            if (!string.IsNullOrEmpty(name))
            {
                name = FormatPropertyName(name);

                WriteProperty("{0}:{1}".FormatWith(name, value.ToString().ToLower()));
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">if set to <c>true</c> [value].</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, bool? value)
        {
            if (value.HasValue)
            {
                WriteProperty(name, value.Value);
            } 
            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">if set to <c>true</c> [value].</param>
        /// <param name="defaultValue">if set to <c>true</c> [default value].</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, bool value, bool defaultValue)
        {
            if (value != defaultValue)
            {
                WriteProperty(name, value);
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and date <seealso cref="DateTime"/>.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, DateTime value)
        {
            if (!string.IsNullOrEmpty(name) && (value != DateTime.MinValue))
            {
                name = FormatPropertyName(name);

                string dateValue = value.ToString("yyyy-MM-ddTHH:mm:ss");

                WriteProperty("{0}:{1}".FormatWith(name, dateValue));
            }
            return this;
        }

        /// <summary>
        /// Write the specified name and only the date of the passed <seealso cref="DateTime"/>.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, DateTime? value)
        {
            if (value.HasValue)
            {
                WriteProperty(name, value.Value);
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="function">The function.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, Func<string> function)
        {
            if (!string.IsNullOrEmpty(name) && (function != null))
            {
                name = FormatPropertyName(name);

                string value = function();
                if (!string.IsNullOrEmpty(value))
                    WriteProperty("{0}:{1}".FormatWith(name, value));
            }

            return this;
        }

        public IJavascriptObjectWriter WriteProperty(string name, IList<Func<string>> functionList)
        {
            if (!string.IsNullOrEmpty(name) && (functionList != null) && functionList.Count > 0)
            {
                name = FormatPropertyName(name);

                WriteProperty("{0}:", name);
                Append("[");
                bool isAppended = false;
                foreach (var function in functionList)
                {
                    string value = function();
                    if (!string.IsNullOrEmpty(value))
                    {
                        if (isAppended)
                            Append(",");
                        Append(value);
                        isAppended = true;
                    }
                }
                Append("]");
            }
            return this;
        }
        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, Action action)
        {
            if (!string.IsNullOrEmpty(name) && (action != null))
            {
                name = FormatPropertyName(name);

                WriteProperty("{0}:".FormatWith(name));
                action();
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="values">The values.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, IList<string> values)
        {
            if (!string.IsNullOrEmpty(name) && values != null && values.Count > 0)
            {
                name = FormatPropertyName(name);

                List<string> stringValues = new List<string>(values.Count);

                foreach (string value in values)
                {
                    stringValues.Add("'{0}'".FormatWith(QuoteString(value)));
                }

                WriteProperty("{0}:[{1}]".FormatWith(name, string.Join(",", stringValues.ToArray())));
            }

            return this;
        }

        /// <summary>
        /// Write the specified name and value to the end of this instance.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="values">The values.</param>
        /// <returns></returns>
        public IJavascriptObjectWriter WriteProperty(string name, IList<int> values)
        {
            if (!string.IsNullOrEmpty(name) && values != null && values.Count > 0)
            {
                name = FormatPropertyName(name);

                List<string> stringValues = new List<string>();

                foreach (int value in values)
                {
                    stringValues.Add(value.ToString());
                }

                WriteProperty("{0}:[{1}]".FormatWith(name, string.Join(",", stringValues.ToArray())));
            }
            return this;
        }


        public IJavascriptObjectWriter WritePropertyCollection<T>(string name, IEnumerable<T> value)
        {
            name = FormatPropertyName(name);

            return WriteProperty("{0}:{1}".FormatWith(name, new JavaScriptSerializer().Serialize(value)));
        }

        public IJavascriptObjectWriter WritePropertyObject(string name, object value)
        {
            name = FormatPropertyName(name);

            if (value != null)
                WriteProperty("{0}:{1}".FormatWith(name, new JavaScriptSerializer().Serialize(value)));
            return this;
        }
        #endregion

        #region Value Text Format
        private static string QuoteString(string value)
        {
            StringBuilder result = new StringBuilder();

            if (!string.IsNullOrEmpty(value))
            {
                int startIndex = 0;
                int count = 0;

                for (int i = 0; i < value.Length; i++)
                {
                    char c = value[i];

                    if (c == '\r' || c == '\t' || c == '\"' || c == '\'' || c == '<' || c == '>' ||
                        c == '\\' || c == '\n' || c == '\b' || c == '\f' || c < ' ')
                    {
                        if (count > 0)
                        {
                            result.Append(value, startIndex, count);
                        }

                        startIndex = i + 1;
                        count = 0;
                    }

                    switch (c)
                    {
                        case '\r':
                            result.Append("\\r");
                            break;
                        case '\t':
                            result.Append("\\t");
                            break;
                        case '\"':
                            result.Append("\\\"");
                            break;
                        case '\\':
                            result.Append("\\\\");
                            break;
                        case '\n':
                            result.Append("\\n");
                            break;
                        case '\b':
                            result.Append("\\b");
                            break;
                        case '\f':
                            result.Append("\\f");
                            break;
                        case '\'':
                        case '>':
                        case '<':
                            AppendAsUnicode(result, c);
                            break;
                        default:
                            if (c < ' ')
                            {
                                AppendAsUnicode(result, c);
                            }
                            else
                            {
                                count++;
                            }

                            break;
                    }
                }

                if (result.Length == 0)
                {
                    result.Append(value);
                }
                else if (count > 0)
                {
                    result.Append(value, startIndex, count);
                }
            }

            return result.ToString();
        }

        private static void AppendAsUnicode(StringBuilder builder, char c)
        {
            builder.Append("\\u");
            builder.AppendFormat(CultureInfo.CurrentCulture, "{0:x4}", (int)c);
        }
        #endregion

        #region OutPut
        public string Output()
        {
            return this.writer.ToString();
        }
        #endregion

        #region Append
        public IJavascriptObjectWriter Append(string content)
        {
            if (!string.IsNullOrEmpty(content))
            {
                this.writer.Append(content);
            }
            return this;
        }
        public IJavascriptObjectWriter AppendLine()
        {
            this.writer.AppendLine();
            return this;
        }
        public IJavascriptObjectWriter AppendLine(string content)
        {
            this.writer.AppendLine(content);
            return this;
        } 
        #endregion


    }
}
